﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Runtime.Serialization;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;

namespace Common.Model
{
    public class CSpielmanager : CBase
    {
        private List<CSpieler> _Spieler;
        private CSpieler _CurrentSpieler;

        private Action<List<CSpieler>, bool> SpielFertigAction;

        public enum GameTyp { SpielerVSSpieler, SpielerVSComputer, ComputerVSComputer }

        #region getter / setter

        public bool SpielFertig
        {
            get
            {
                return (CSpielfeld.Instance.reCalculatePossibleFields(CurrentSpieler).Count < 1 && (CSpielfeld.Instance.reCalculatePossibleFields((Spieler.IndexOf(CurrentSpieler) == 0 ? Spieler[1] : Spieler[0])).Count < 1));
            }
        }

        public bool ShowPossibleMoves { get; set; }

        public CProtokoll Protokoll { get; set; }

        public List<CSpieler> Spieler
        {
            get
            {
                return _Spieler;
            }
            private set
            {
                _Spieler = value;
            }
        }

        /// <summary>
        /// spieler, welcher zur zeit am zug ist.
        /// </summary>
        public CSpieler CurrentSpieler
        {
            get
            {
                if (_CurrentSpieler == null && Spieler.Count > 0) _CurrentSpieler = Spieler.First();
                return _CurrentSpieler;
            }
            private set
            {
                _CurrentSpieler = value;
            }
        }

        private static CSpielmanager _Instance;
        public static CSpielmanager Instance
        {
            get{
                if (_Instance == null)
                    _Instance = new CSpielmanager();
                return _Instance;
            }
        }

        #endregion

        #region konstruktor

        private CSpielmanager() {
            _Spieler = new List<CSpieler>();
        }

        #endregion

        #region add/remove spieler
        /// <summary>
        /// generiert die spieler entsprechend dem spieler modus und fügt diese hinzu
        /// </summary>
        /// <param name="t"></param>
        public void addSpieler(GameTyp t)
        {
            switch (t)
            {
                case GameTyp.SpielerVSSpieler:
                    Spieler.Add(new CSpieler(CSpielfeld.FieldColor.Black));
                    Spieler.Add(new CSpieler(CSpielfeld.FieldColor.White));
                    break;
                case GameTyp.SpielerVSComputer:
                    Spieler.Add(new CSpieler(CSpielfeld.FieldColor.Black));
                    Spieler.Add(new CKISpieler(CSpielfeld.FieldColor.White));
                    break;
                case GameTyp.ComputerVSComputer:
                    Spieler.Add(new CKISpieler(CSpielfeld.FieldColor.Black));
                    Spieler.Add(new CKISpieler(CSpielfeld.FieldColor.White));
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// fügt einen spieler hinzu
        /// </summary>
        /// <param name="sps"></param>
        /// <returns></returns>
        public bool addSpieler(List<CSpieler> sps)
        {
            if (Spieler.Count + sps.Count > 2)
            {
                return false;
            }

            sps.ForEach((sp) => Spieler.Add(sp));
            return true;
        }
        /// <summary>
        /// fügt einen spieler hinzu
        /// </summary>
        /// <param name="sps"></param>
        /// <returns></returns>
        public bool addSpieler(CSpieler sps)
        {
            return addSpieler(new List<CSpieler>() { sps });
        }

        /// <summary>
        /// entfernt einen spieler
        /// </summary>
        /// <param name="sps"></param>
        public void removeSpieler(List<CSpieler> sps)
        {
            sps.ForEach((sp) => Spieler.Remove(sp));
        }

        /// <summary>
        /// entfernt einen spieler
        /// </summary>
        /// <param name="sps"></param>
        public void removeSpieler(CSpieler sps)
        {
            Spieler.Remove(sps);
        }
        
        /// <summary>
        /// entfernt alle spieler
        /// </summary>
        public void removeAllSpieler()
        {
            Spieler.Clear();
        }

        #endregion

        #region startgame
        /// <summary>
        /// startet das spiel im angegebenen modus
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="spielFertig">Methode welche aufgerufen wird, wenn das spiel beendet ist. Dabei finden sich im ersten Parameter eine Liste mit Spielern, absteigend nach Punkten sortiert wieder, im zweiten Parameter ein Bool, welcher angibt ob das Spiel unentschieden geendet hat.</param>
        /// <param name="size">Spielfeldgrösse</param>
        /// <param name="currentSpieler">Spieler, welcher am Zug ist</param>
        /// <param name="showPossibleMoves">Giebt an ob mögliche Züge angezeigt werden sollen</param>
        /// <returns>giebt zurück ob der spielstart erfolgreich war</returns>
        public bool startGame(Action<List<CSpieler>, bool> spielFertig, bool showPossibleMoves, CSpielfeld.GameMode mode = CSpielfeld.GameMode.Default, int size = 8, CSpieler currentSpieler = null)
        {
            if (Spieler.Count != 2)
                return false;

            SpielFertigAction = spielFertig;

            this.CurrentSpieler = currentSpieler;

            CSpielfeld.Instance.reInit(mode, size);
            CSpielfeld.Instance.reCalculatePossibleFields(CurrentSpieler);

            this.ShowPossibleMoves = showPossibleMoves;

            Protokoll = new CProtokoll(this.Spieler, showPossibleMoves);
            Protokoll.AddEintrag(CSpielfeld.Instance, CurrentSpieler);

            return true;
        }
        
        /// <summary>
        /// startet das spiel im angegebenen modus
        /// <param name="mode"></param>
        /// <param name="spielFertig">Methode welche aufgerufen wird, wenn das spiel beendet ist. Dabei finden sich im ersten Parameter eine Liste mit Spielern, absteigend nach Punkten sortiert wieder, im zweiten Parameter ein Bool, welcher angibt ob das Spiel unentschieden geendet hat.</param>
        /// <param name="size">Spielfeldgrösse</param>
        /// <param name="currentSpieler">Spieler, welcher am Zug ist</param>
        /// <param name="showPossibleMoves">Giebt an ob mögliche Züge angezeigt werden sollen</param>
        /// <returns>giebt zurück ob der spielstart erfolgreich war</returns>
        public bool startGame(List<CSpieler> sps, Action<List<CSpieler>, bool> spielFertig, bool showPossibleMoves, CSpielfeld.GameMode mode = CSpielfeld.GameMode.Default, int size = 8, CSpieler currentSpieler = null)
        {
            removeAllSpieler();
            addSpieler(sps);
            return startGame(spielFertig, showPossibleMoves, mode, size, currentSpieler);
        }
        #endregion

        /// <summary>
        /// nimmt einen spielzug am angegebenen punkt vor
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public bool turn(CPoint p)
        {
            bool success = CSpielfeld.Instance.turn(p, CurrentSpieler);
            if (success)
            {

                CurrentSpieler = (Spieler.IndexOf(CurrentSpieler) == 0 ? Spieler[1] : Spieler[0]);

                Protokoll.AddEintrag(CSpielfeld.Instance, CurrentSpieler);

                if (this.SpielFertig)
                {
                    Spieler = Spieler.OrderByDescending(s => s.Punkte).ToList();
                    SpielFertigAction(Spieler, Spieler[0].Punkte == Spieler[1].Punkte);
                    CurrentSpieler = null;
                }
            }
            return success;
        }

        #region protokoll
        #region saveprotokoll
        /// <summary>
        /// speichert das protokoll asynchron im angegebenen pfad
        /// </summary>
        /// <param name="path"></param>
        /// <param name="callback"></param>
        public void SaveProtokollAsync(string path = null, Action<Exception> callback = null)
        {
            (new Thread(() => SaveProtokoll(path, callback))).Start();
        }
        /// <summary>
        /// speichert das protokoll im angegebenen pfad
        /// </summary>
        /// <param name="path"></param>
        /// <param name="callback"></param>
        public void SaveProtokoll(string path = null, Action<Exception> callback = null)
        {
            try
            {
                Protokoll.export(path);
                if (callback != null)
                    callback(null);
            }
            catch (Exception e)
            {
                if (callback != null)
                    callback(e);
            }
        }
        #endregion
        #region import protokoll
        /// <summary>
        /// importiert das protokoll und beginnt das spiel / setzt dieses fort
        /// </summary>
        /// <param name="spielFertig"></param>
        /// <param name="path"></param>
        public void import(Action<List<CSpieler>, bool> spielFertig, string path)
        {
            try
            {
                Protokoll = CProtokoll.import(path);

                SpielFertigAction = spielFertig;

                this.Spieler = Protokoll.Spieler;
                Protokoll.KISpieler.ForEach((s) => this.Spieler.Add(s));

                this.ShowPossibleMoves = Protokoll.showPossibleMoves;

                protokollDo(Protokoll[CProtokoll.Moves.Current]);
            }
            catch (Exception)
            {
                throw;
            }
        }
        #endregion
        /// <summary>
        /// geht im protokoll einen schritt zurück
        /// </summary>
        public void undo()
        {
            protokollDo(Protokoll[CProtokoll.Moves.Previous]);
        }
        /// <summary>
        /// geht im protokoll einen schritt nach vorne
        /// </summary>
        public void redo()
        {
            protokollDo(Protokoll[CProtokoll.Moves.Next]);
        }
        /// <summary>
        /// aktualisiert alle eigenschaften um dem entsprechendem protokolleintrag zu entsprechen
        /// </summary>
        /// <param name="p"></param>
        private void protokollDo(CProtokollEintrag p)
        {
            if (p.CurrentSpielerIsKI)
            {
                this.CurrentSpieler = (CKISpieler)Protokoll.KISpieler[p.CurrentSpielerIndex];
            }
            else
            {
                this.CurrentSpieler = (CSpieler)Protokoll.Spieler[p.CurrentSpielerIndex];
            }
            CSpielfeld.Instance.Feld = p.Feld;
            CSpielfeld.Instance.reCalculatePossibleFields(CurrentSpieler);
        }
        #endregion

    }
}
 